<?php
/**
 * Created by PhpStorm.
 * User: whwyy
 * Date: 2018/3/30 0030
 * Time: 14:09
 */

namespace BeReborn\Database;


use BeReborn\Base\Component;
use BeReborn\Base\Config;
use BeReborn\Database\Mysql\Schema;
use BeReborn\Database\Orm\Select;
use BeReborn\Exception\ComponentException;
use BeReborn\Exception\ConfigException;
use BeReborn\Service\Common\ServerRequest;
use BeReborn\Service\Common\ServerTask;
use Exception;
use PDO;

/**
 * Class Connection
 * @package BeReborn\Database
 */
class Connection extends Component
{
	const TRANSACTION_COMMIT = 'transaction::commit';
	const TRANSACTION_ROLLBACK = 'transaction::rollback';

	public $id = 'db';
	public $cds = '';
	public $password = '';
	public $username = '';
	public $charset = 'utf-8';
	public $tablePrefix = '';


	public $database = '';

	public $timeout = 1900;

	public $maxNumber = 100;

	/**
	 * @var array
	 *
	 * @example [
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 * ]
	 */
	public $masterConfig = [];

	/**
	 * @var bool
	 * enable database cache
	 */
	public $enableCache = false;
	public $cacheDriver = 'redis';

	/**
	 * @var array
	 *
	 * @example [
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 *    ['cds' => 'mysql:dbname=dbname;host=127.0.0.1', 'username' => 'root', 'password' => 'root'],
	 * ]
	 */
	public $slaveConfig = [];

	/** @var Schema $_schema */
	private $_schema = null;


	/**
	 * @throws Exception
	 */
	public function init()
	{
		on(ServerTask::AFTER_TASK, [$this, 'disconnect']);
		on(ServerRequest::AFTER_REQUEST, [$this, 'clear_connection']);
		on('SERVER_BEFORE_WORKER', [$this, 'init_channel_connection']);
	}


	/**
	 * @throws ConfigException
	 * @throws Exception
	 */
	public function init_channel_connection()
	{
		$data = Config::get('databases', false, ['pool' => ['max' => 10, 'min' => 1]]);

		$connections = \BeReborn::$app->connections;
		$connections->initConnections($this->cds, true, $data['pool']['max']);
		$connections->initConnections($this->slaveConfig['cds'], false, $data['pool']['max']);
	}


	/**
	 * @throws Exception
	 */
	public function enablingTransactions()
	{
		if (!Db::transactionsActive()) {
			return;
		}
		$this->beginTransaction();
		on(Connection::TRANSACTION_COMMIT, [$this, 'commit'], false, true);
		on(Connection::TRANSACTION_ROLLBACK, [$this, 'rollback'], false, true);
	}

	/**
	 * @param null $sql
	 * @return PDO
	 * @throws Exception
	 */
	public function getConnect($sql = NULL)
	{
		return $this->getPdo($sql);
	}

	/**
	 * @param $sql
	 * @return PDO
	 * @throws Exception
	 */
	private function getPdo($sql)
	{
		if ($this->isWrite($sql)) {
			$connect = $this->masterInstance();
		} else {
			$connect = $this->slaveInstance();
		}
		return $connect;
	}

	/**
	 * @return mixed|object|Schema
	 * @throws Exception
	 */
	public function getSchema()
	{
		if ($this->_schema === null) {
			$this->_schema = \BeReborn::createObject([
				'class' => Schema::class,
				'db'    => $this
			]);
		}
		return $this->_schema;
	}

	/**
	 * @param $sql
	 * @return bool
	 */
	public function isWrite($sql)
	{
		if (empty($sql)) return false;

		$prefix = strtolower(mb_substr($sql, 0, 6));

		return in_array($prefix, ['insert', 'update', 'delete']);
	}

	/**
	 * @return mixed|null
	 * @throws ComponentException
	 */
	public function getCacheDriver()
	{
		if (!$this->enableCache) {
			return null;
		}
		return \BeReborn::$app->get($this->cacheDriver);
	}

	/**
	 * @return PDO
	 * @throws Exception
	 */
	public function masterInstance()
	{
		$config = [
			'cds'      => $this->cds,
			'username' => $this->username,
			'password' => $this->password,
			'database' => $this->database
		];
		$pool = \BeReborn::$app->connections;
		return $pool->get($config, true);
	}


	/**
	 * @return PDO
	 * @throws Exception
	 */
	public function slaveInstance()
	{
		if (empty($this->slaveConfig) || $this->slaveConfig['cds'] == $this->cds) {
			return $this->masterInstance();
		}

		$pool = \BeReborn::$app->connections;
		return $pool->get($this->slaveConfig, false);
	}


	/**
	 * @return $this
	 * @throws Exception
	 */
	public function beginTransaction()
	{
		$pool = \BeReborn::$app->connections;
		$pool->beginTransaction($this->cds);
		return $this;
	}

	/**
	 * @return bool
	 * @throws Exception
	 */
	public function inTransaction()
	{
		$pool = \BeReborn::$app->connections;
		return $pool->inTransaction($this->cds);
	}

	/**
	 * @throws Exception
	 * 事务回滚
	 */
	public function rollback()
	{
		$pool = \BeReborn::$app->connections;
		$pool->rollback($this->cds);
	}

	/**
	 * @throws Exception
	 * 事务提交
	 */
	public function commit()
	{
		$pool = \BeReborn::$app->connections;
		$pool->commit($this->cds);
	}

	/**
	 * @param $sql
	 * @return PDO
	 * @throws Exception
	 */
	public function refresh($sql)
	{
        $pool = \BeReborn::$app->connections;
        $this->disconnect();
        if ($this->isWrite($sql) || empty($this->slaveConfig) || Db::transactionsActive()) {
            return $pool->reset([
                'cds'      => $this->cds,
                'username' => $this->username,
                'password' => $this->password,
                'database' => $this->database
            ], true);
        } else if ($this->slaveConfig['cds'] == $this->cds) {
            return $pool->reset([
                'cds'      => $this->cds,
                'username' => $this->username,
                'password' => $this->password,
                'database' => $this->database
            ], false);
        } else {
            return $pool->reset($this->slaveConfig, false);
        }
	}

	/**
	 * @param $sql
	 * @param array $attributes
	 * @return Command
	 * @throws
	 */
	public function createCommand($sql = null, $dbName = '', $attributes = [])
	{
		if (!empty($dbName)) {
			$sql = $this->clear($dbName, $sql);
		}
		$command = new Command([
			'db' => $this
		]);
		$command->setSql($sql);
		return $command->bindValues($attributes);
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function clear($dbname, $sql)
	{
		$substr = strtoupper(substr($sql, 0, 6));
		switch ($substr) {
			case 'SELECT':
			case 'SHOW F':
			case 'DELETE':
				return $this->selectMatch($dbname, $sql);
			case 'UPDATE' :
				return $this->updateMatch($dbname, $sql);
			case 'INSERT' :
				return $this->insertMatch($dbname, $sql);
			case 'TRUNCA' :
				return $this->truncateMatch($dbname, $sql);
			default:
				return $sql;
		}
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function selectMatch($dbname, $sql)
	{
		return preg_replace('/FROM\s+(\w+)\s+/', 'FROM `' . $dbname . '`.$1 ', $sql);
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function innerJoinMatch($dbname, $sql)
	{
		return preg_replace('/(INNER|LEFT|RIGHT) JOIN\s+(\w+)\s/', '$1 JOIN `' . $dbname . '`.$2 ', $sql);
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function updateMatch($dbname, $sql)
	{
		return preg_replace('/UPDATE\s+(\w+)\s+SET/', 'UPDATE `' . $dbname . '`.$1 SET', $sql);
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function insertMatch($dbname, $sql)
	{
		return preg_replace('/INSERT INTO\s+(\w+)/', 'INSERT INTO `' . $dbname . '`.$1', $sql);
	}


	/**
	 * @param $dbname
	 * @param $sql
	 * @return array|string|null
	 */
	private function truncateMatch($dbname, $sql)
	{
		return preg_replace('/TRUNCATE\s+(\w+)\s+/', 'TRUNCATE `' . $dbname . '`.$1', $sql);
	}


	/**
	 * @return Select
	 * @throws Exception
	 */
	public function getBuild()
	{
		return $this->getSchema()->getQueryBuilder();
	}

	/**
	 *
	 * 回收链接
	 * @throws
	 */
	public function release()
	{
		$connections = \BeReborn::$app->connections;

		$connections->release($this->cds, true);
		$connections->release($this->slaveConfig['cds'], false);
	}


	/**
	 * 临时回收
	 */
	public function recovery()
	{
		$connections = \BeReborn::$app->connections;

		$connections->release($this->cds, true);
		$connections->release($this->slaveConfig['cds'], false);
	}

	/**
	 *
	 * 回收链接
	 * @throws
	 */
	public function clear_connection()
	{
		$connections = \BeReborn::$app->connections;

		$connections->release($this->cds, true);
		$connections->release($this->slaveConfig['cds'], false);
	}


	/**
	 * @throws Exception
	 */
	public function disconnect()
	{
		$connections = \BeReborn::$app->connections;
		$connections->disconnect($this->cds);
		$connections->disconnect($this->slaveConfig['cds']);
	}

}
